import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class LexParser {

    private static final Map<String, TokenType> keywordMap = new HashMap<String, TokenType>() {
        {
            put("int", TokenType.Int);
            put("float", TokenType.Float);
            put("char", TokenType.Char);
            put("void", TokenType.Void);
            put("if", TokenType.If);
            put("else", TokenType.Else);
            put("switch", TokenType.Switch);
            put("case", TokenType.Case);
            put("for", TokenType.For);
            put("while", TokenType.While);
            put("do", TokenType.Do);
            put("break", TokenType.Break);
            put("return", TokenType.Return);
            put("null", TokenType.Null);
            put("true", TokenType.True);
            put("false", TokenType.False);
            put("main", TokenType.Main);
        }
    };

    /**
     * 解析一行
     *
     * @param line
     * @return token 对象集合
     */
    public static List<Token> parser(String line,int row,String fileName) {
        List<Token> tokenList = new ArrayList<>();
        State currState = State.Init;
        State befWordState = State.Init;  // 上一个单词处于的状态
        int beginIndex= 0;
        int endIndex = 0;
        // line拼接一个空格 方面处理最后一个单词
        line += " ";
        for (int i = 0; i < line.length(); i++) {
            // 记录单词在本行的开始列标
            endIndex = i;

            // 消除空格 排除 ' ' 这种可能
            if (i == 0 && line.charAt(i) == ' '){
                // 记录单词在本行的开始列标
                beginIndex++;
                continue;
            }
            else if (currState == State.Init && i > 0 && line.charAt(i) == ' ' && line.charAt(i - 1) != '\''){
                // 经过空格，将上一状态清空
                befWordState = State.Init;
                // 记录单词在本行的开始列标
                beginIndex++;
                continue;
            }

            // 前一个字符所处的状态
            State befCharState = currState;
            // 获取当前字符状态
            currState = transform(currState, line.charAt(i));

            // 当前状态结束 或 出错
            if (currState == State.Error) {
                // 获取当前判断的单词
                String word = line.substring(beginIndex, endIndex);
                Token token = getTokenType(befCharState, word);
                // 若上一字符状态为关键字或者标识符 需要判断前一单词状态是否为字面量
                // 例如 int 4b = 5;  b状态下需要判断前面的4是否为关键字或标识符  如果是就报错
                if(befCharState == State.Identifier || getKeywordMap(word) != null){
                    switch (befWordState){
                        case Float_Literal:
                        case Int_Literal:
                            String errMsg = String.format("Lexical error,unable to parse '%c',in line %d,column %d of %s",
                                    line.charAt(beginIndex-1),row,beginIndex-1,fileName);
                            try {
                                throw new Exception(errMsg);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            return null;
                        case Char_Literal:
                            errMsg = String.format("Lexical error,unable to parse '%c',in line %d,column %d of %s",
                                    line.charAt(beginIndex),row,beginIndex,fileName);
                            try {
                                throw new Exception(errMsg);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            return null;
                    }
                }
                // 判断上一状态是不是终结态  不为null就是终结态
                if(token != null){
                    // 存储token相关信息
                    token.setRow(row);
                    token.setBeginCol(beginIndex);
                    token.setEndCol(endIndex-1);
                    tokenList.add(token);
                    // 下标、状态更新
                    befWordState = befCharState;  // 记录上一个单词的状态
                    beginIndex = endIndex;
                    currState = State.Init;
                    // line 下标回退
                    i--;
                }
                else {
                    String errMsg = String.format("Lexical error,unable to parse '%c',in line %d,column %d of %s",
                            line.charAt(endIndex),row,endIndex,fileName);
                    try {
                        throw new Exception(errMsg);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return null;
                }
            }
        }
        return tokenList;
    }

    /**
     * 状态转换
     *
     * @param state    当前状态
     * @param currChar 下一个字符
     * @return 一个新的状态
     */
    private static State transform(State state, char currChar) {
        switch (state) {
            case Init:
                if (isLetter(currChar) || currChar == '_') return State.Identifier;
                if (isNumber(currChar)) return State.Int_Literal;
                if (currChar == '\'') return State.Char_Literal_1;
                switch (currChar) {
                    case '{':
                    case '}':
                    case '[':
                    case ']':
                    case ',':
                    case ';':
                        return State.Separator;
                    case '+':
                    case '-':
                    case '*':
                    case '/':
                    case '=':
                    case '(':
                    case ')':
                    case '&':
                    case '|':
                    case '>':
                    case '<':
                    case ':':
                        return State.Operator;
                }
                return State.Error;

            // 关键字 以 _ 或者字母开头 ，后面跟着字母或者数字或者下划线
            case Identifier:
                if (isLetter(currChar) || currChar == '_' || isNumber(currChar)) return State.Identifier;
                return State.Error;

            // int 字面量 也可能加.变成float 字面量
            case Int_Literal:
                if (isNumber(currChar)) return State.Int_Literal;
                if (currChar == '.') return State.Float_Literal_1;
                return State.Error;

            // 处理float 字面量
            case Float_Literal_1:
            case Float_Literal:
                if (isNumber(currChar)) return State.Float_Literal;
                return State.Error;

            // char 字面量
            case Char_Literal_1:
                return State.Char_Literal_2;
            case Char_Literal_2:
                if (currChar == '\'') return State.Char_Literal;
                return State.Error;
        }
        return State.Error;
    }

    /**
     * 根据状态和单词获取token对象
     *
     * @param state
     * @param word
     * @return token对象
     */
    private static Token getTokenType(State state, String word) {
        switch (state) {
            case Identifier:
                // 判断是否为关键字
                Token token = getKeywordMap(word);
                if (token != null) return token;
                return new Token(TokenType.Identifier, word);
            case Float_Literal:
                return new Token(TokenType.Float_Literal, word);
            case Int_Literal:
                return new Token(TokenType.Int_Literal, word);
            case Char_Literal:
                return new Token(TokenType.Char_Literal, word);
            case Separator:
                return new Token(TokenType.Separator, word);
            case Operator:
                return new Token(TokenType.Operator, word);
            default:
                return null;
        }
    }


    /**
     * 判断标识符是不是关键字
     *
     * @param word
     * @return token对象
     */
    private static Token getKeywordMap(String word) {
        TokenType tokenType = keywordMap.get(word);
        if (tokenType == null) {
            return null;
        }
        return new Token(tokenType, word);
    }


    private static boolean isNumber(char ch) {
        return ch >= '0' && ch <= '9';
    }

    private static boolean isLetter(char ch) {
        return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z';
    }

}
